<!DOCTYPE html>
<html lang='zh-CN'>

<head>
  <meta name="generator" content="Hexo 6.3.0">
  <meta name="hexo-theme" content="https://github.com/xaoxuu/hexo-theme-stellar/tree/1.19.0">
  <meta charset="utf-8">
  

  <meta http-equiv='x-dns-prefetch-control' content='on' />
  <link rel='dns-prefetch' href='https://gcore.jsdelivr.net'>
  <link rel="preconnect" href="https://gcore.jsdelivr.net" crossorigin>
  <link rel='dns-prefetch' href='//unpkg.com'>

  <meta name="renderer" content="webkit">
  <meta name="force-rendering" content="webkit">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="HandheldFriendly" content="True" >
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
  <meta name="theme-color" content="#f8f8f8">
  
  <title>Spring Cloud Gateway详解 - 愔颂</title>

  
    <meta name="description" content="微服务网关，为什么需要API网关？网关的工作流程，断言、动态路由、过滤器、token认证。">
<meta property="og:type" content="article">
<meta property="og:title" content="Spring Cloud Gateway详解">
<meta property="og:url" content="https://farhills.gitee.io/2023/10/05/Spring-Cloud-Gateway%E8%AF%A6%E8%A7%A3/index.html">
<meta property="og:site_name" content="愔颂">
<meta property="og:description" content="微服务网关，为什么需要API网关？网关的工作流程，断言、动态路由、过滤器、token认证。">
<meta property="og:locale" content="zh_CN">
<meta property="og:image" content="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695801024153-ef5f8634-22b0-4735-aa8e-dcca23a8c42f.png">
<meta property="og:image" content="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695801280475-4ab78d81-67f6-407e-bdcf-5d79486f0ccb.png">
<meta property="og:image" content="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695801368480-aee2d8aa-f8f9-4b18-b4f3-ffdd5c4552f7.png">
<meta property="og:image" content="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695801489096-a854884e-e20a-442d-aa9a-dd5bbd2d94b1.png">
<meta property="og:image" content="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695801881857-e92e323b-5271-434a-95c6-f4f2b152efa3.png">
<meta property="og:image" content="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695802218816-2f640936-3f23-4819-bf77-039083df9c5d.png">
<meta property="og:image" content="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695802561955-04c057a5-34dc-4bfe-82ea-6f74ec89b3d6.png">
<meta property="article:published_time" content="2023-10-05T03:13:35.022Z">
<meta property="article:modified_time" content="2023-10-10T01:38:06.398Z">
<meta property="article:author" content="远岫">
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695801024153-ef5f8634-22b0-4735-aa8e-dcca23a8c42f.png">
  
  
  
  

  <!-- feed -->
  

  
    
<link rel="stylesheet" href="/css/main.css">

  

  
    <link rel="shortcut icon" href="https://z1.ax1x.com/2023/10/05/pPXijyT.png">
  

  

  


  
</head>

<body>
  




  <div class='l_body' id='start'>
    <aside class='l_left' layout='post'>
    

  

<header class="header"><div class="logo-wrap"><a class="avatar" href="/about/"><div class="bg" style="opacity:0;background-image:url(https://gcore.jsdelivr.net/gh/cdn-x/placeholder@1.0.4/avatar/round/rainbow64@3x.webp);"></div><img no-lazy class="avatar" src="https://s1.ax1x.com/2022/11/12/ziJjfK.jpg" onerror="javascript:this.classList.add('error');this.src='https://gcore.jsdelivr.net/gh/cdn-x/placeholder@1.0.4/image/2659360.svg';"></a><a class="title" href="/"><div class="main" ff="title">愔颂</div></a></div>

<nav class="menu dis-select"><a class="nav-item active" href="/">文章</a><a class="nav-item" href="/friends/">收藏</a><a class="nav-item" href="/about/">关于</a></nav>
</header>


<div class="widgets">
<widget class="widget-wrapper search"><div class="widget-body"><div class="search-wrapper" id="search"><form class="search-form"><input type="text" class="search-input" id="search-input" data-filter="/blog/" placeholder="文章搜索"><svg t="1670596976048" class="icon search-icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2676" width="200" height="200"><path d="M938.2 832.6L723.8 618.1c-2.5-2.5-5.3-4.4-7.9-6.4 36.2-55.6 57.3-121.8 57.3-193.1C773.3 222.8 614.6 64 418.7 64S64 222.8 64 418.6c0 195.9 158.8 354.6 354.6 354.6 71.3 0 137.5-21.2 193.2-57.4 2 2.7 3.9 5.4 6.3 7.8L832.5 938c14.6 14.6 33.7 21.9 52.8 21.9 19.1 0 38.2-7.3 52.8-21.8 29.2-29.1 29.2-76.4 0.1-105.5M418.7 661.3C284.9 661.3 176 552.4 176 418.6 176 284.9 284.9 176 418.7 176c133.8 0 242.6 108.9 242.6 242.7 0 133.7-108.9 242.6-242.6 242.6" p-id="2677"></path></svg></form><div id="search-result"></div><div class="search-no-result">没有找到内容！</div></div></div></widget>


<widget class="widget-wrapper toc single" id="data-toc"><div class="widget-header cap dis-select"><span class="name">Spring Cloud Gateway详解</span></div><div class="widget-body fs14"><div class="doc-tree active"><ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#1%E3%80%81%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81API%E7%BD%91%E5%85%B3%EF%BC%9F"><span class="toc-text">1、为什么需要API网关？</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#2%E3%80%81%E5%B7%A5%E4%BD%9C%E6%B5%81%E7%A8%8B"><span class="toc-text">2、工作流程</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#3%E3%80%81%E6%96%AD%E8%A8%80"><span class="toc-text">3、断言</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#4%E3%80%81%E5%8A%A8%E6%80%81%E8%B7%AF%E7%94%B1"><span class="toc-text">4、动态路由</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#5%E3%80%81%E8%BF%87%E6%BB%A4%E5%99%A8"><span class="toc-text">5、过滤器</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#6%E3%80%81token%E8%AE%A4%E8%AF%81"><span class="toc-text">6、token认证</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#7%E3%80%81%E8%B7%A8%E5%9F%9F%E5%A4%84%E7%90%86"><span class="toc-text">7、跨域处理</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#8%E3%80%81%E7%BD%91%E5%85%B3%E8%81%9A%E5%90%88Swagger-UI"><span class="toc-text">8、网关聚合Swagger-UI</span></a></li></ol></div></div></widget>




</div>


    </aside>
    <div class='l_main'>
      

      



<div class="bread-nav fs12"><div id="breadcrumb"><a class="cap breadcrumb" href="/">主页</a><span class="sep"></span><a class="cap breadcrumb" href="/">文章</a><span class="sep"></span><a class="cap breadcrumb-link" href="/categories/Spring-Cloud/">Spring Cloud</a></div><div id="post-meta">发布于&nbsp;<time datetime="2023-10-05T03:13:35.022Z">2023-10-05</time></div></div>

<article class='md-text content post'>
<h1 class="article-title"><span>Spring Cloud Gateway详解</span></h1>
<meta name="referrer" content="no-referrer"/>

<p>微服务网关，为什么需要API网关？网关的工作流程，断言、动态路由、过滤器、token认证。</p>
<span id="more"></span>

<h2 id="1、为什么需要API网关？"><a href="#1、为什么需要API网关？" class="headerlink" title="1、为什么需要API网关？"></a>1、为什么需要API网关？</h2><p>在 SpringCloud 微服务架构中，往往有多个微服务，这些微服务可能部署在不同的机器上，而且一个微服务可能会扩容成多个相同的微服务，组成微服务集群。</p>
<p>这种情况下，会存在如下问题：</p>
<ul>
<li>如果需要添加鉴权功能，则需要对每个微服务进行改造。</li>
<li>如果需要对流量进行控制，则需要对每个微服务进行改造。</li>
<li>跨域问题，需要对每个微服务进行改造。</li>
<li>存在安全问题，每个微服务需要暴露自己的 Endpoint 给客户端。</li>
</ul>
<p>Endpoint 就是服务的访问地址 + 端口，比如下面的地址：</p>
<p><a target="_blank" rel="noopener" href="http://order.passjava.cn:8000/">http://order.passjava.cn:8000</a></p>
<ul>
<li>灰度发布、动态路由需要对每个微服务进行改造。</li>
</ul>
<p>这个问题的痛点是各个微服务都是一个入口，有没有办法统一入口呢？</p>
<p>解决这个问题的方式就是在客户端和服务器之间加个中间商就好了呀，只有中间商一个入口，这个中间商就是网关。</p>
<h2 id="2、工作流程"><a href="#2、工作流程" class="headerlink" title="2、工作流程"></a>2、工作流程</h2><p>Gateway 的工作流程如下图所示：</p>
<div class="tag-plugin image"><div class="image-bg"><img src="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695801024153-ef5f8634-22b0-4735-aa8e-dcca23a8c42f.png" fancybox="true"/></div></div>

<p>① <strong>路由判断</strong>；客户端的请求到达网关后，先经过 Gateway Handler Mapping 处理，这里面会做断言（Predicate）判断，看下符合哪个路由规则，这个路由映射后端的某个服务。</p>
<p>② <strong>请求过滤</strong>：然后请求到达 Gateway Web Handler，这里面有很多过滤器，组成过滤器链（Filter Chain），这些过滤器可以对请求进行拦截和修改，比如添加请求头、参数校验等等，有点像净化污水。然后将请求转发到实际的后端服务。这些过滤器逻辑上可以称作 Pre-Filters，Pre 可以理解为“在…之前”。</p>
<p>③ <strong>服务处理</strong>：后端服务会对请求进行处理。</p>
<p>④ <strong>响应过滤</strong>： 后端处理完结果后，返回给 Gateway 的过滤器再次做处理，逻辑上可以称作 Post-Filters，Post 可以理解为“在…之后”。</p>
<p>⑤ <strong>响应返回</strong>：响应经过过滤处理后，返回给客户端。</p>
<p>小结：客户端的请求先通过匹配规则找到合适的路由，就能映射到具体的服务。然后请求经过过滤器处理后转发给具体的服务，服务处理后，再次经过过滤器处理，最后返回给客户端。</p>
<h2 id="3、断言"><a href="#3、断言" class="headerlink" title="3、断言"></a>3、断言</h2><p>断言（Predicate）这个词听起来极其深奥，它是一种编程术语，我们生活中根本就不会用它。说白了它就是对一个表达式进行 if 判断，结果为真或假，如果为真则做这件事，否则做那件事。</p>
<p>在 Gateway 中，如果客户端发送的请求满足了断言的条件，则映射到指定的路由器，就能转发到指定的服务上进行处理。</p>
<p>断言配置的示例如下，配置了两个路由规则，有一个 predicates 断言配置，当请求 url 中包含 api&#x2F;thirdparty，就匹配到了第一个路由 route_thirdparty。</p>
<div class="tag-plugin image"><div class="image-bg"><img src="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695801280475-4ab78d81-67f6-407e-bdcf-5d79486f0ccb.png" fancybox="true"/></div></div>



<p>接下来我们看下 Route 路由和 Predicate 断言的对应关系：</p>
<div class="tag-plugin image"><div class="image-bg"><img src="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695801368480-aee2d8aa-f8f9-4b18-b4f3-ffdd5c4552f7.png" fancybox="true"/></div></div>

<ul>
<li><strong>一对多</strong>：一个路由规则可以包含多个断言。如上图中路由 Route1 配置了三个断言 Predicate。</li>
<li><strong>同时满足</strong>：如果一个路由规则中有多个断言，则需要同时满足才能匹配。如上图中路由 Route2 配置了两个断言，客户端发送的请求必须同时满足这两个断言，才能匹配路由 Route2。</li>
<li><strong>第一个匹配成功</strong>：如果一个请求可以匹配多个路由，则映射第一个匹配成功的路由。如上图所示，客户端发送的请求满足 Route3 和 Route4 的断言，但是 Route3 的配置在配置文件中靠前，所以只会匹配 Route3。</li>
</ul>
<p>常见的 Predicate 断言配置如下所示，假设匹配路由成功后，转发到 <a target="_blank" rel="noopener" href="http://localhost:9001/">http://localhost:9001</a>。</p>
<div class="tag-plugin image"><div class="image-bg"><img src="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695801489096-a854884e-e20a-442d-aa9a-dd5bbd2d94b1.png" fancybox="true"/></div></div>



<p><strong>代码演示：</strong></p>
<p>下面演示 Gateway 中通过断言来匹配路由的例子。</p>
<ul>
<li>新建一个 Maven 工程，引入 Gateway 依赖。</li>
</ul>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.cloud<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-cloud-starter-gateway<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br></pre></td></tr></table></figure>

<ul>
<li>新建 application.yml 文件，添加 Gateway 的路由规则。</li>
</ul>
<figure class="highlight properties"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring</span>:<span class="string"></span></span><br><span class="line">  <span class="attr">cloud</span>:<span class="string"></span></span><br><span class="line">    <span class="attr">gateway</span>:<span class="string"></span></span><br><span class="line">      <span class="attr">routes</span>:<span class="string"></span></span><br><span class="line">        <span class="attr">-</span> <span class="string">id: route_qq</span></span><br><span class="line">          <span class="attr">uri</span>: <span class="string">http://www.qq.com</span></span><br><span class="line">          <span class="attr">predicates</span>:<span class="string"></span></span><br><span class="line">            <span class="attr">-</span> <span class="string">Query=url,qq</span></span><br><span class="line">        <span class="attr">-</span> <span class="string">id: route_baidu</span></span><br><span class="line">          <span class="attr">uri</span>: <span class="string">http://www.baidu.com</span></span><br><span class="line">          <span class="attr">predicates</span>:<span class="string"></span></span><br><span class="line">            <span class="attr">-</span> <span class="string">Query=url,baidu</span></span><br><span class="line"><span class="attr">server</span>:<span class="string"></span></span><br><span class="line">  <span class="attr">port</span>: <span class="string">8060 复制复制失败复制成功</span></span><br></pre></td></tr></table></figure>



<p>第一条路由规则：断言为 Query&#x3D;url,qq，表示当请求路径中包含 url&#x3D;qq，则跳转到<a target="_blank" rel="noopener" href="http://www.qq.com/">http://www.qq.com</a></p>
<p>第二条路由规则：当请求路径中包含 url&#x3D;baidu，则跳转到<a target="_blank" rel="noopener" href="http://www.baidu.com/">http://www.baidu.com</a></p>
<h2 id="4、动态路由"><a href="#4、动态路由" class="headerlink" title="4、动态路由"></a>4、动态路由</h2><p>在微服务架构中，我们不会直接通过 IP + 端口的方式访问微服务，而是通过服务名的方式来访问。如下图所示，微服务中加入了注册中心，多个微服务将自己注册到了注册中心，这样注册中心就保存了服务名和 IP+端口的映射关系。</p>
<div class="tag-plugin image"><div class="image-bg"><img src="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695801881857-e92e323b-5271-434a-95c6-f4f2b152efa3.png" fancybox="true"/></div></div>



<p>网关经过断言匹配到一个路由后，将请求转发给指定 uri，这个 uri 可以配置成 微服务的名字，比如 passjava-member。</p>
<p>那么这个服务名具体要转发到哪个 IP 地址和端口上呢？这个就依赖注册中心的注册表了，Gateway 从注册中心拉取注册表，就能知道服务名对应具体的 IP + 端口，如果一个服务部署了多台机器，则还可以通过负载均衡进行请求的转发。</p>
<p>对应的配置为 uri 字段如下所示</p>
<div class="tag-plugin image"><div class="image-bg"><img src="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695802218816-2f640936-3f23-4819-bf77-039083df9c5d.png" fancybox="true"/></div></div>uri: lb://passjava-question，表示将请求转发给 passjava-question 微服务，且支持负载均衡。lb 是 loadbalance（负载均衡) 单词的缩写。

<p>那什么叫动态路由呢？</p>
<p>当 passjava-question 服务添加一个微服务，或者 IP 地址更换了，Gateway 都是可以感知到的，但是配置是不需要更新的。这里的动态指的是微服务的集群个数、IP 和端口是动态可变的。</p>
<h2 id="5、过滤器"><a href="#5、过滤器" class="headerlink" title="5、过滤器"></a>5、过滤器</h2><p>过滤器 Filter 按照请求和响应可以分为两种：Pre 类型和 Post 类型。</p>
<p><strong>Pre 类型</strong>：在请求被转发到微服务之前，对请求进行拦截和修改，例如参数校验、权限校验、流量监控、日志输出以及协议转换等操作。</p>
<p><strong>Post 类型</strong>：微服务处理完请求后，返回响应给网关，网关可以再次进行处理，例如修改响应内容或响应头、日志输出、流量监控等。</p>
<p>另外一种分类是按照过滤器 Filter 作用的范围进行划分：</p>
<p><strong>GlobalFilter</strong>：全局过滤器，应用在所有路由上的过滤器。</p>
<p><strong>GatewayFilter</strong>：局部过滤器，应用在单个路由或一组路由上的过滤器。</p>
<h2 id="6、token认证"><a href="#6、token认证" class="headerlink" title="6、token认证"></a>6、token认证</h2><p>在用 Gateway 做登录认证的时候，通常需要我们自定义一个全局过滤器做登录认证。</p>
<p>比如客户端登录时，将用户名和密码发送给网关，网关转发给认证服务器后，如果账号密码正确，则拿到一个 JWT token，然后客户端再访问应用服务时，先将请求发送给网关，网关统一做 JWT 认证，如果 JWT 符合条件，再将请求转发给应用服务。</p>
<p>原理如下图所示：</p>
<div class="tag-plugin image"><div class="image-bg"><img src="https://cdn.nlark.com/yuque/0/2023/png/36098302/1695802561955-04c057a5-34dc-4bfe-82ea-6f74ec89b3d6.png" fancybox="true"/></div></div>



<h2 id="7、跨域处理"><a href="#7、跨域处理" class="headerlink" title="7、跨域处理"></a>7、跨域处理</h2><p>在Spring Cloud Gateway中实现跨域处理的步骤如下：</p>
<ol>
<li><p>首先，在你的Spring Cloud Gateway项目中添加CORS过滤器。你可以创建一个类来实现 <code>GlobalFilter</code> 接口，然后在过滤器中添加跨域处理的逻辑。</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">CorsFilter</span> <span class="keyword">implements</span> <span class="title class_">GlobalFilter</span>, Ordered &#123;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">ALLOWED_HEADERS</span> <span class="operator">=</span> <span class="string">&quot;x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">ALLOWED_METHODS</span> <span class="operator">=</span> <span class="string">&quot;GET, PUT, POST, DELETE, OPTIONS&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">ALLOWED_ORIGIN</span> <span class="operator">=</span> <span class="string">&quot;*&quot;</span>;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">String</span> <span class="variable">MAX_AGE</span> <span class="operator">=</span> <span class="string">&quot;7200&quot;</span>; <span class="comment">// 2 hours</span></span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> Mono&lt;Void&gt; <span class="title function_">filter</span><span class="params">(ServerWebExchange exchange, GatewayFilterChain chain)</span> &#123;</span><br><span class="line">    <span class="type">ServerHttpRequest</span> <span class="variable">request</span> <span class="operator">=</span> exchange.getRequest();</span><br><span class="line">    <span class="keyword">if</span> (!request.getHeaders().containsKey(HttpHeaders.ORIGIN)) &#123;</span><br><span class="line">        <span class="keyword">return</span> chain.filter(exchange);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="type">ServerHttpResponse</span> <span class="variable">response</span> <span class="operator">=</span> exchange.getResponse();</span><br><span class="line">    <span class="type">HttpHeaders</span> <span class="variable">headers</span> <span class="operator">=</span> response.getHeaders();</span><br><span class="line">    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, ALLOWED_ORIGIN);</span><br><span class="line">    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, ALLOWED_HEADERS);</span><br><span class="line">    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, ALLOWED_METHODS);</span><br><span class="line">    headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (request.getMethod() == HttpMethod.OPTIONS) &#123;</span><br><span class="line">        response.setStatusCode(HttpStatus.OK);</span><br><span class="line">        <span class="keyword">return</span> Mono.empty();</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> chain.filter(exchange);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <span class="type">int</span> <span class="title function_">getOrder</span><span class="params">()</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> Ordered.HIGHEST_PRECEDENCE;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

</li>
<li><p>然后，确保你的Spring Cloud Gateway应用程序已经启用了跨域请求。在你的配置文件（例如<code>application.yml</code> ）中添加以下配置：</p>
</li>
</ol>
  <figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">spring:</span></span><br><span class="line">  <span class="attr">cloud:</span></span><br><span class="line">    <span class="attr">gateway:</span></span><br><span class="line">      <span class="attr">globalcors:</span></span><br><span class="line">        <span class="attr">corsConfigurations:</span></span><br><span class="line">          <span class="string">&#x27;[/**]&#x27;</span><span class="string">:</span> <span class="comment"># 任意路径都允许配置的跨域</span></span><br><span class="line">            <span class="attr">allowedOrigins:</span> <span class="string">&quot;*&quot;</span> <span class="comment"># 允许所有请求来源进行跨域</span></span><br><span class="line">            <span class="attr">allowedMethods:</span> <span class="comment"># 允许所有请求方式进行跨域</span></span><br><span class="line">              <span class="bullet">-</span> <span class="string">GET</span></span><br><span class="line">              <span class="bullet">-</span> <span class="string">POST</span></span><br><span class="line">              <span class="bullet">-</span> <span class="string">PUT</span></span><br><span class="line">              <span class="bullet">-</span> <span class="string">DELETE</span></span><br><span class="line">            <span class="attr">allowedHeaders:</span> <span class="comment"># 允许所有请求头进行跨域</span></span><br><span class="line">              <span class="bullet">-</span> <span class="string">&quot;*&quot;</span></span><br><span class="line">            <span class="attr">allowCredentials:</span> <span class="literal">true</span> <span class="comment"># 允许携带cookie进行跨域</span></span><br><span class="line">            <span class="attr">maxAge:</span> <span class="number">3600</span></span><br></pre></td></tr></table></figure>

<p>  这样就允许了所有路径的跨域请求。</p>
<ol start="3">
<li>最后，重新启动你的Spring Cloud Gateway应用程序。现在，它应该能够处理跨域请求了。</li>
</ol>
<h2 id="8、网关聚合Swagger-UI"><a href="#8、网关聚合Swagger-UI" class="headerlink" title="8、网关聚合Swagger-UI"></a>8、网关聚合Swagger-UI</h2><p><a target="_blank" rel="noopener" href="https://juejin.cn/post/7089970649292079112">springcloud：gateway聚合swagger </a></p>





</article>

<div class="related-wrap reveal" id="read-next"><section class="body"><div class="item" id="prev"><div class="note">较新文章</div><a href="/2023/10/05/Swagger%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3%E4%BD%BF%E7%94%A8/">Swagger接口文档使用</a></div><div class="item" id="next"><div class="note">较早文章</div><a href="/2023/10/05/Redis%E5%AE%9E%E7%8E%B0%E6%8E%92%E8%A1%8C%E6%A6%9C/">Redis实现排行榜</a></div></section></div>






  <div class='related-wrap md-text reveal' id="comments">
    <section class='header cmt-title cap theme'>
      快来参与讨论吧
    </section>
    <section class='body cmt-body giscus'>
      

<svg class="loading" style="vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2709"><path d="M832 512c0-176-144-320-320-320V128c211.2 0 384 172.8 384 384h-64zM192 512c0 176 144 320 320 320v64C300.8 896 128 723.2 128 512h64z" p-id="2710"></path></svg>

<div id="giscus" data-repo="echoalways/giscus" data-repo-id="R_kgDOKjJRiQ" data-category="Announcements" data-category-id="DIC_kwDOKjJRic4CaUxu" data-mapping="pathname" data-strict="0" data-reactions-enabled="1" data-emit-metadata="0" data-input-position="top" data-theme="preferred_color_scheme" data-lang="zh-CN" data-loading="lazy" crossorigin="anonymous"></div>

    </section>
  </div>



      
<footer class="page-footer reveal fs12"><hr><div class="text" style="text-align:center;"><p>云无心以出岫@远岫♥</p>
<div><span id="timeDate">载入天数...</span><span id="times">载入时分秒...</span>，<span id="busuanzi_container_site_pv">总访问量: <span id="busuanzi_value_site_pv"></span>次</span>，<span id="busuanzi_container_site_uv">访客数: <span id="busuanzi_value_site_uv"></span>人</span></div></div></footer>

<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>
<script>
  function createtime() {
    var now = new Date();
    var grt= new Date("10/03/2023 18:40:00");
    now.setTime(now.getTime()+250);
    days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days);
    hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum); hnum = Math.floor(hours);
    if(String(hnum).length ==1 ){hnum = "0" + hnum;} minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
    mnum = Math.floor(minutes); if(String(mnum).length ==1 ){mnum = "0" + mnum;}
    seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
    snum = Math.round(seconds); if(String(snum).length ==1 ){snum = "0" + snum;}
    document.getElementById("timeDate").innerHTML = "本站已运行 "+dnum+" 天 ";
    document.getElementById("times").innerHTML = hnum + " 小时 " + mnum + " 分 " + snum + " 秒";
  };
  setInterval("createtime()",250);
</script>

      <div class='float-panel mobile-only blur' style='display:none'>
  <button type='button' class='sidebar-toggle mobile' onclick='sidebar.toggle()'>
    <svg class="icon" style="width: 1em; height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15301"><path d="M566.407 808.3c26.9-0.1 49.3-20.8 51.6-47.6-1.9-27.7-23.9-49.7-51.6-51.6h-412.6c-28.2-1.4-52.6 19.5-55.5 47.6 2.3 26.8 24.6 47.5 51.6 47.6h416.5v4z m309.3-249.9c26.9-0.1 49.3-20.8 51.6-47.6-2.2-26.8-24.6-47.5-51.6-47.6h-721.9c-27.7-2.8-52.5 17.4-55.3 45.1-0.1 0.8-0.1 1.7-0.2 2.5 0.9 27.2 23.6 48.5 50.7 47.6H875.707z m-103.1-245.9c26.9-0.1 49.3-20.8 51.6-47.6-0.4-28.3-23.2-51.1-51.5-51.6h-618.9c-29.5-1.1-54.3 21.9-55.5 51.4v0.2c1.4 27.8 25.2 49.2 53 47.8 0.8 0 1.7-0.1 2.5-0.2h618.8z" p-id="15302"></path><path d="M566.407 808.3c26.9-0.1 49.3-20.8 51.6-47.6-1.9-27.7-23.9-49.7-51.6-51.6h-412.6c-28.2-1.4-52.6 19.5-55.5 47.6 1.9 27.7 23.9 49.7 51.6 51.6h416.5z m309.3-249.9c26.9-0.1 49.3-20.8 51.6-47.6-2.2-26.8-24.6-47.5-51.6-47.6h-721.9c-27.7-2.8-52.5 17.4-55.3 45.1-0.1 0.8-0.1 1.7-0.2 2.5 0.9 27.2 23.6 48.5 50.7 47.6H875.707z m-103.1-245.9c26.9-0.1 49.3-20.8 51.6-47.6-0.4-28.3-23.2-51.1-51.5-51.6h-618.9c-29.5-1.1-54.3 21.9-55.5 51.4v0.2c1.4 27.8 25.2 49.2 53 47.8 0.8 0 1.7-0.1 2.5-0.2h618.8z" p-id="15303"></path></svg>
  </button>
</div>

    </div>
  </div>
  <div class='scripts'>
    <script type="text/javascript">
  const stellar = {
    // 懒加载 css https://github.com/filamentgroup/loadCSS
    loadCSS: (href, before, media, attributes) => {
      var doc = window.document;
      var ss = doc.createElement("link");
      var ref;
      if (before) {
        ref = before;
      } else {
        var refs = (doc.body || doc.getElementsByTagName("head")[0]).childNodes;
        ref = refs[refs.length - 1];
      }
      var sheets = doc.styleSheets;
      if (attributes) {
        for (var attributeName in attributes) {
          if (attributes.hasOwnProperty(attributeName)) {
            ss.setAttribute(attributeName, attributes[attributeName]);
          }
        }
      }
      ss.rel = "stylesheet";
      ss.href = href;
      ss.media = "only x";
      function ready(cb) {
        if (doc.body) {
          return cb();
        }
        setTimeout(function () {
          ready(cb);
        });
      }
      ready(function () {
        ref.parentNode.insertBefore(ss, before ? ref : ref.nextSibling);
      });
      var onloadcssdefined = function (cb) {
        var resolvedHref = ss.href;
        var i = sheets.length;
        while (i--) {
          if (sheets[i].href === resolvedHref) {
            return cb();
          }
        }
        setTimeout(function () {
          onloadcssdefined(cb);
        });
      };
      function loadCB() {
        if (ss.addEventListener) {
          ss.removeEventListener("load", loadCB);
        }
        ss.media = media || "all";
      }
      if (ss.addEventListener) {
        ss.addEventListener("load", loadCB);
      }
      ss.onloadcssdefined = onloadcssdefined;
      onloadcssdefined(loadCB);
      return ss;
    },

    // 从 butterfly 和 volantis 获得灵感
    loadScript: (src, opt) => new Promise((resolve, reject) => {
      var script = document.createElement('script');
      if (src.startsWith('/')){
        src = stellar.config.root + src.substring(1);
      }
      script.src = src;
      if (opt) {
        for (let key of Object.keys(opt)) {
          script[key] = opt[key]
        }
      } else {
        // 默认异步，如果需要同步，第二个参数传入 {} 即可
        script.async = true
      }
      script.onerror = reject
      script.onload = script.onreadystatechange = function() {
        const loadState = this.readyState
        if (loadState && loadState !== 'loaded' && loadState !== 'complete') return
        script.onload = script.onreadystatechange = null
        resolve()
      }
      document.head.appendChild(script)
    }),

    // https://github.com/jerryc127/hexo-theme-butterfly
    jQuery: (fn) => {
      if (typeof jQuery === 'undefined') {
        stellar.loadScript(stellar.plugins.jQuery).then(fn)
      } else {
        fn()
      }
    }
  };
  stellar.version = '1.19.0';
  stellar.github = 'https://github.com/xaoxuu/hexo-theme-stellar/tree/1.19.0';
  stellar.config = {
    date_suffix: {
      just: '刚刚',
      min: '分钟前',
      hour: '小时前',
      day: '天前',
      month: '个月前',
    },
    root : '/',
  };

  // required plugins (only load if needs)
  stellar.plugins = {
    jQuery: 'https://gcore.jsdelivr.net/npm/jquery@3.6.2/dist/jquery.min.js'
  };

  if ('local_search') {
    stellar.search = {};
    stellar.search.service = 'local_search';
    if (stellar.search.service == 'local_search') {
      let service_obj = Object.assign({}, {"field":"all","path":"/search.json","content":true,"sort":"-date"});
      stellar.search[stellar.search.service] = service_obj;
    }
  }

  // stellar js
  stellar.plugins.stellar = Object.assign({"sites":"/js/plugins/sites.js","friends":"/js/plugins/friends.js","ghinfo":"/js/plugins/ghinfo.js","timeline":"/js/plugins/timeline.js","linkcard":"/js/plugins/linkcard.js","fcircle":"/js/plugins/fcircle.js","weibo":"/js/plugins/weibo.js"});

  stellar.plugins.marked = Object.assign("https://cdn.bootcdn.net/ajax/libs/marked/4.0.18/marked.min.js");
  // optional plugins
  if ('false' == 'true') {
    stellar.plugins.lazyload = Object.assign({"enable":false,"js":"https://gcore.jsdelivr.net/npm/vanilla-lazyload@17.8.3/dist/lazyload.min.js","transition":"blur"});
  }
  if ('true' == 'true') {
    stellar.plugins.swiper = Object.assign({"enable":true,"css":"https://unpkg.com/swiper@8.4.5/swiper-bundle.min.css","js":"https://unpkg.com/swiper@8.4.5/swiper-bundle.min.js"});
  }
  if ('' == 'true') {
    stellar.plugins.scrollreveal = Object.assign({"enable":null,"js":"https://gcore.jsdelivr.net/npm/scrollreveal@4.0.9/dist/scrollreveal.min.js","distance":"8px","duration":500,"interval":100,"scale":1});
  }
  if ('true' == 'true') {
    stellar.plugins.preload = Object.assign({"enable":true,"service":"flying_pages","instant_page":"https://gcore.jsdelivr.net/gh/volantis-x/cdn-volantis@4.1.2/js/instant_page.js","flying_pages":"https://gcore.jsdelivr.net/gh/gijo-varghese/flying-pages@2.1.2/flying-pages.min.js"});
  }
  if ('true' == 'true') {
    stellar.plugins.fancybox = Object.assign({"enable":true,"js":"https://gcore.jsdelivr.net/npm/@fancyapps/ui@4.0/dist/fancybox.umd.js","css":"https://gcore.jsdelivr.net/npm/@fancyapps/ui@4.0/dist/fancybox.css","selector":".swiper-slide img"});
  }
  if ('false' == 'true') {
    stellar.plugins.heti = Object.assign({"enable":false,"css":"https://unpkg.com/heti@0.9.2/umd/heti.min.css","js":"https://unpkg.com/heti@0.9.2/umd/heti-addon.min.js"});
  }
  if ('true' == 'true') {
    stellar.plugins.copycode = Object.assign({"enable":true,"js":"/js/plugins/copycode.js","default_text":"Copy","success_text":"Copied"});
  }
</script>

<!-- required -->

  
<script src="/js/main.js" async></script>



<!-- optional -->

  <script>
  function loadJS() {
    const els = document.querySelectorAll("#comments #giscus");
    if (els.length === 0) return;
    els.forEach((el, i) => {
      try {
        el.innerHTML = '';
      } catch (error) {
        console.log(error);
      }
      var script = document.createElement('script');
      script.src = 'https://giscus.app/client.js';
      script.async = true;
      for (let key of Object.keys(el.attributes)) {
        let attr = el.attributes[key];
        if (['class', 'id'].includes(attr.name) === false) {
          script.setAttribute(attr.name, attr.value);
        }
      }
      el.appendChild(script);
    });
  }
  window.addEventListener('DOMContentLoaded', (event) => {
    loadJS();
  });
</script>




<!-- inject -->


  </div>
</body>
</html>
